Micron Document
rns.moscow 🟥 [git]


Commit 8d64759ef727bd9bee11859fcb348dafd96e1d99


Parents : 477901a
Author : Nickie Deuxyeux <nikolay@dvoeglazov.ru>
Date : 2026-08-04T15:11:36+03:00

Retry ESP32 flashing at lower baud rates for Windows serial noise

Some CP2102 + ESP32-D0WD boards (e.g. DIY-V1) fail to survive esptool's
baud switch to 921600 on Windows: the CP210x driver hands stale buffered
bytes to the next read after the port re-open, which esptool-js reads as
corrupt reply data and aborts with "Unable to verify flash chip
connection". The same boards flash fine on Linux, where the tty just
drops the buffered bytes.

espConnect() now steps down through 921600 -> 460800 -> 115200,
flushing driver input after each baud switch and reconnecting from
scratch one speed lower when a link-noise error is seen, remembering
the working speed so repeat connects don't pay for the failed attempt.

Changes

1 files changed, 108 insertions(+), 39 deletions(-)

M htdocs/index.html +108 -39

Diff

diff --git a/htdocs/index.html b/htdocs/index.html
index 7ecbcfc..5d81e74 100644
--- a/htdocs/index.html
+++ b/htdocs/index.html
@@ -1110,6 +1110,16 @@ const NRF52_VIDS = [0x239a, 0x1915, 0x2886];
// nRF52 device.
const NRF52_ERASER_ZIP = ERASE + 'nRF52_Universal_Eraser_DFU.zip';
+// Speeds espConnect() tries, fastest first. Anything past the first entry
+// is a Windows workaround: older ESP32-D0WD boards (DIY-V1) behind a
+// CP2102 do not reliably survive the switch to 921600 there, while the
+// same board flashes fine on Linux. See espConnect() for the details.
+const ESP_BAUD_LADDER = [921600, 460800, 115200];
+
+// Time given to the OS/driver to deliver whatever it buffered across the
+// port close/re-open of a baud switch, before that buffer is dropped.
+const ESP_BAUD_SETTLE_MS = 250;
+
function fc(name, sz, boot0) {
return {
flash_size: sz,
@@ -1734,6 +1744,11 @@ Vue.createApp({
showOtherFirmwares: false,
scrolled: false,
+ // Baud rate that last completed an esptool handshake on this
+ // machine+board. espConnect() starts from it so a board that needed
+ // to fall back once doesn't pay for the failed attempt again.
+ espBaudrate: null,
+
log: [],
progressLineIdx: -1,
screenImage: null,
@@ -2883,6 +2898,89 @@ Vue.createApp({
this.addLog('ok', ' ✓ ', 'DFU transfer complete, device rebooting...');
},
+ // ── esptool connection ────────────────────────────────────
+ // Releases the Web Serial locks esptool-js holds and closes the port,
+ // so the port can be re-opened by a retry (or handed back to the OS).
+ async releaseEspTransport(transport, port) {
+ try {
+ if (transport?.reader) {
+ await transport.reader.cancel();
+ transport.reader.releaseLock();
+ }
+ if (transport?.writer) transport.writer.releaseLock();
+ } catch(e) {}
+ try { await port.close(); } catch(e) {}
+ },
+
+ // True for the failures that mean "the link itself is bad", as opposed
+ // to "no device answered". Only these are worth retrying slower.
+ isEspLinkNoiseError(e) {
+ const msg = String(e?.message || e);
+ return /invalid head of packet|serial noise|verify flash chip connection|timed out waiting for packet/i.test(msg);
+ },
+
+ // Connects esptool-js to an ESP32 in download mode, working down
+ // ESP_BAUD_LADDER until the link holds.
+ //
+ // Once the stub is up, esptool-js raises the speed by closing and
+ // re-opening the serial port (ESPLoader.changeBaud). A Linux tty
+ // discards anything still in flight across that close; the Windows
+ // CP210x driver (SiLabs 11.x) hands those stale bytes to the next
+ // read instead. esptool-js then sees them as the start of the reply
+ // to its first post-switch command and gives up with
+ // Invalid head of packet (0x80): Possible serial noise or corruption
+ // surfaced as "Unable to verify flash chip connection", right after
+ // it logged "Changed". That is why this only ever bites on Windows.
+ //
+ // Guard 1: drop whatever the driver buffered across a baud switch
+ // before the first command goes out at the new speed. Guard 2: if the
+ // link still doesn't come up, reconnect from scratch one speed lower —
+ // 921600 is simply out of reach for some CP2102 + ESP32-D0WD boards
+ // (DIY-V1) on Windows, no matter how clean the handover is.
+ async espConnect(port, onLine) {
+ // Resume at the speed that worked last time and keep stepping down
+ // from there — never back up to one already known to be too fast.
+ const start = Math.max(0, ESP_BAUD_LADDER.indexOf(this.espBaudrate));
+ const ladder = ESP_BAUD_LADDER.slice(start);
+
+ let lastErr;
+ for (let i = 0; i < ladder.length; i++) {
+ const baudrate = ladder[i];
+ const transport = new window.Transport(port, false);
+ transport.trace = () => {};
+ const esploader = new window.ESPLoader({
+ transport, baudrate, debugLogging: false, enableTracing: false,
+ terminal: {
+ clean() {},
+ writeLine: (d) => { if (d?.trim()) onLine(d.trim()); },
+ write() {},
+ },
+ });
+
+ const changeBaud = esploader.changeBaud.bind(esploader);
+ esploader.changeBaud = async () => {
+ await changeBaud();
+ await Utils.sleepMillis(ESP_BAUD_SETTLE_MS);
+ transport.flushInput();
+ };
+
+ try {
+ const chip = await esploader.main();
+ this.espBaudrate = baudrate;
+ return { transport, esploader, chip };
+ } catch(e) {
+ lastErr = e;
+ await this.releaseEspTransport(transport, port);
+ const next = ladder[i + 1];
+ if (next === undefined || !this.isEspLinkNoiseError(e)) throw e;
+ this.addLog('warn', ' ! ', String(e.message || e));
+ this.addLog('warn', ' ! ', `Serial link unstable at ${baudrate} baud — reconnecting at ${next}`);
+ await Utils.sleepMillis(500); // Windows needs a moment to release the port
+ }
+ }
+ throw lastErr;
+ },
+
// ── ESP32 flash via esptool-js ────────────────────────────
async doEsp32Flash(port, blob, flashConfig, flashStep = 'Flashing...') {
if (!window.ESPLoader) throw new Error('esptool-js not loaded');
@@ -2917,20 +3015,11 @@ Vue.createApp({
let transport;
try {
- transport = new window.Transport(port, false);
- transport.trace = () => {};
+ let esploader, chip;
+ ({ transport, esploader, chip } = await this.espConnect(
+ port, (line) => this.addLog('info', ' > ', line)
+ ));
- const esploader = new window.ESPLoader({
- transport,
- baudrate: 921600,
- debugLogging: false,
- enableTracing: false,
- terminal: {
- clean() {},
- writeLine: (d) => { if (d && d.trim()) this.addLog('info', ' > ', d.trim()); },
- write() {},
- },
- });
let writeFileIdx = 0;
esploader.terminal.writeLine = (d) => {
if (!d || !d.trim()) return;
@@ -2943,7 +3032,6 @@ Vue.createApp({
this.addLog('info', ' > ', line);
};
- const chip = await esploader.main();
this.addLog('ok', ' ✓ ', `Chip: ${chip}`);
this.addLog('cmd', '$ ', `Flashing firmware (${flashConfig.flash_size})...`);
this.currentStep = flashStep;
@@ -3009,14 +3097,7 @@ Vue.createApp({
}
} finally {
- try {
- if (transport?.reader) {
- await transport.reader.cancel();
- transport.reader.releaseLock();
- }
- if (transport?.writer) transport.writer.releaseLock();
- } catch(e) {}
- try { await port.close(); } catch(e) {}
+ await this.releaseEspTransport(transport, port);
}
},
@@ -3227,25 +3308,17 @@ Vue.createApp({
let transport;
try {
- transport = new window.Transport(port, false);
- transport.trace = () => {};
- const esploader = new window.ESPLoader({
- transport, baudrate: 921600, debugLogging: false, enableTracing: false,
- terminal: { clean(){}, writeLine(d){}, write(d){} },
- });
- esploader.terminal.writeLine = (d) => {
- if (d && d.trim()) this.addLog('info', ' > ', d.trim());
- };
- let chip;
+ const onLine = (line) => this.addLog('info', ' > ', line);
+ let esploader, chip;
try {
- chip = await esploader.main();
+ ({ transport, esploader, chip } = await this.espConnect(port, onLine));
} catch(e) {
if (e.message && /open|port|access/i.test(e.message)) {
this.addLog('warn', ' ! ', `Port error: ${e.message}`);
this.addLog('cmd', '$ ', 'Attempting bootloader reset...');
await this.enterBootloader(port);
this.addLog('cmd', '$ ', 'Retrying connection...');
- chip = await esploader.main();
+ ({ transport, esploader, chip } = await this.espConnect(port, onLine));
} else {
throw e;
}
@@ -3258,11 +3331,7 @@ Vue.createApp({
this.addLog('err', ' ✗ ', String(e.message || e));
this.statusType = 'error'; this.statusMsg = String(e.message || e);
} finally {
- try {
- if (transport?.reader) { await transport.reader.cancel(); transport.reader.releaseLock(); }
- if (transport?.writer) transport.writer.releaseLock();
- } catch(e) {}
- try { await port.close(); } catch(e) {}
+ await this.releaseEspTransport(transport, port);
this.disconnectPort();
this.isWorking = false; this.currentStep = ''; this.flashProgress = 0;
}

Served by rngit 1.4.2 - Generated in 0.02s